/*-------------------------------------------------------------------------
 * drawElements Base Portability Library
 * -------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Memory management.
 *//*--------------------------------------------------------------------*/

#include "deMemory.h"
#include "deInt32.h"

#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>

#define DE_ALIGNED_MALLOC_POSIX		0
#define DE_ALIGNED_MALLOC_WIN32		1
#define DE_ALIGNED_MALLOC_GENERIC	2

#if (DE_OS == DE_OS_UNIX) || ((DE_OS == DE_OS_ANDROID) && (DE_ANDROID_API >= 21))
#	define DE_ALIGNED_MALLOC DE_ALIGNED_MALLOC_POSIX
#       if defined(__FreeBSD__)
#	        include <stdlib.h>
#       else
#	        include <malloc.h>
#       endif
#elif (DE_OS == DE_OS_WIN32)
#	define DE_ALIGNED_MALLOC DE_ALIGNED_MALLOC_WIN32
#	include <malloc.h>
#else
#	define DE_ALIGNED_MALLOC DE_ALIGNED_MALLOC_GENERIC
#endif

#if defined(DE_VALGRIND_BUILD)
#	include <valgrind/valgrind.h>
#	if defined(HAVE_VALGRIND_MEMCHECK_H)
#		include <valgrind/memcheck.h>
#	endif
#endif

DE_BEGIN_EXTERN_C

/*--------------------------------------------------------------------*//*!
 * \brief Allocate a chunk of memory.
 * \param numBytes	Number of bytes to allocate.
 * \return Pointer to the allocated memory (or null on failure).
 *//*--------------------------------------------------------------------*/
void* deMalloc (size_t numBytes)
{
	void* ptr;

	DE_ASSERT(numBytes > 0);

	ptr = malloc((size_t)numBytes);

#if defined(DE_DEBUG)
	/* Trash memory in debug builds (if under Valgrind, don't make it think we're initializing data here). */

	if (ptr)
		memset(ptr, 0xcd, numBytes);

#if defined(DE_VALGRIND_BUILD) && defined(HAVE_VALGRIND_MEMCHECK_H)
	if (ptr && RUNNING_ON_VALGRIND)
	{
		VALGRIND_MAKE_MEM_UNDEFINED(ptr, numBytes);
	}
#endif
#endif

	return ptr;
}

/*--------------------------------------------------------------------*//*!
 * \brief Allocate a chunk of memory and initialize it to zero.
 * \param numBytes	Number of bytes to allocate.
 * \return Pointer to the allocated memory (or null on failure).
 *//*--------------------------------------------------------------------*/
void* deCalloc (size_t numBytes)
{
	void* ptr = deMalloc(numBytes);
	if (ptr)
		deMemset(ptr, 0, numBytes);
	return ptr;
}

/*--------------------------------------------------------------------*//*!
 * \brief Reallocate a chunk of memory.
 * \param ptr		Pointer to previously allocated memory block
 * \param numBytes	New size in bytes
 * \return Pointer to the reallocated (and possibly moved) memory block
 *//*--------------------------------------------------------------------*/
void* deRealloc (void* ptr, size_t numBytes)
{
	return realloc(ptr, numBytes);
}

/*--------------------------------------------------------------------*//*!
 * \brief Free a chunk of memory.
 * \param ptr	Pointer to memory to free.
 *//*--------------------------------------------------------------------*/
void deFree (void* ptr)
{
	free(ptr);
}

#if (DE_ALIGNED_MALLOC == DE_ALIGNED_MALLOC_GENERIC)

typedef struct AlignedAllocHeader_s
{
	void*	basePtr;
	size_t	numBytes;
} AlignedAllocHeader;

DE_INLINE AlignedAllocHeader* getAlignedAllocHeader (void* ptr)
{
	const size_t	hdrSize		= sizeof(AlignedAllocHeader);
	const deUintptr	hdrAddr		= (deUintptr)ptr - hdrSize;

	return (AlignedAllocHeader*)hdrAddr;
}

#endif

void* deAlignedMalloc (size_t numBytes, size_t alignBytes)
{
#if (DE_ALIGNED_MALLOC == DE_ALIGNED_MALLOC_POSIX)
	/* posix_memalign() requires that alignment must be 2^N * sizeof(void*) */
	const size_t	ptrAlignedAlign	= deAlignSize(alignBytes, sizeof(void*));
	void*			ptr				= DE_NULL;

	DE_ASSERT(deIsPowerOfTwoSize(alignBytes) && deIsPowerOfTwoSize(ptrAlignedAlign / sizeof(void*)));

	if (posix_memalign(&ptr, ptrAlignedAlign, numBytes) == 0)
	{
		DE_ASSERT(ptr);
		return ptr;
	}
	else
	{
		DE_ASSERT(!ptr);
		return DE_NULL;
	}

#elif (DE_ALIGNED_MALLOC == DE_ALIGNED_MALLOC_WIN32)
	DE_ASSERT(deIsPowerOfTwoSize(alignBytes));

	return _aligned_malloc(numBytes, alignBytes);

#elif (DE_ALIGNED_MALLOC == DE_ALIGNED_MALLOC_GENERIC)
	void* const	basePtr	= deMalloc(numBytes + alignBytes + sizeof(AlignedAllocHeader));

	DE_ASSERT(deIsPowerOfTwoSize(alignBytes));

	if (basePtr)
	{
		void* const					alignedPtr	= deAlignPtr((void*)((deUintptr)basePtr + sizeof(AlignedAllocHeader)), alignBytes);
		AlignedAllocHeader* const	hdr			= getAlignedAllocHeader(alignedPtr);

		hdr->basePtr	= basePtr;
		hdr->numBytes	= numBytes;

		return alignedPtr;
	}
	else
		return DE_NULL;
#else
#	error "Invalid DE_ALIGNED_MALLOC"
#endif
}

void* deAlignedRealloc (void* ptr, size_t numBytes, size_t alignBytes)
{
#if (DE_ALIGNED_MALLOC == DE_ALIGNED_MALLOC_WIN32)
	return _aligned_realloc(ptr, numBytes, alignBytes);

#elif (DE_ALIGNED_MALLOC == DE_ALIGNED_MALLOC_GENERIC) || (DE_ALIGNED_MALLOC == DE_ALIGNED_MALLOC_POSIX)
	if (ptr)
	{
		if (numBytes > 0)
		{
#	if (DE_ALIGNED_MALLOC == DE_ALIGNED_MALLOC_GENERIC)
			const size_t				oldSize	= getAlignedAllocHeader(ptr)->numBytes;
#	else /* DE_ALIGNED_MALLOC_GENERIC */
			const size_t				oldSize	= malloc_usable_size(ptr);
#	endif

			DE_ASSERT(deIsAlignedPtr(ptr, alignBytes));

			if (oldSize < numBytes || oldSize > numBytes*2)
			{
				/* Create a new alloc if original is smaller, or more than twice the requested size */
				void* const	newPtr	= deAlignedMalloc(numBytes, alignBytes);

				if (newPtr)
				{
					const size_t	copyBytes	= numBytes < oldSize ? numBytes : oldSize;

					deMemcpy(newPtr, ptr, copyBytes);
					deAlignedFree(ptr);

					return newPtr;
				}
				else
					return DE_NULL;
			}
			else
				return ptr;
		}
		else
		{
			deAlignedFree(ptr);
			return DE_NULL;
		}
	}
	else
		return deAlignedMalloc(numBytes, alignBytes);

#else
#	error "Invalid DE_ALIGNED_MALLOC"
#endif
}

void deAlignedFree (void* ptr)
{
#if (DE_ALIGNED_MALLOC == DE_ALIGNED_MALLOC_POSIX)
	free(ptr);

#elif (DE_ALIGNED_MALLOC == DE_ALIGNED_MALLOC_WIN32)
	_aligned_free(ptr);

#elif (DE_ALIGNED_MALLOC == DE_ALIGNED_MALLOC_GENERIC)
	if (ptr)
	{
		AlignedAllocHeader* const	hdr	= getAlignedAllocHeader(ptr);

		deFree(hdr->basePtr);
	}
#else
#	error "Invalid DE_ALIGNED_MALLOC"
#endif
}

char* deStrdup (const char* str)
{
#if (DE_COMPILER == DE_COMPILER_MSC)
	return _strdup(str);
#elif (DE_OS == DE_OS_OSX) || (DE_OS == DE_OS_IOS)
	/* For some reason Steve doesn't like stdrup(). */
	size_t	len		= strlen(str);
	char*	copy	= malloc(len+1);
	memcpy(copy, str, len);
	copy[len] = 0;
	return copy;
#else
	return strdup(str);
#endif
}

void deMemory_selfTest (void)
{
	static const struct
	{
		size_t		numBytes;
		size_t		alignment;
	} s_alignedAllocCases[] =
	{
		{ 1,		1		},
		{ 1,		2		},
		{ 1,		256		},
		{ 1,		4096	},
		{ 547389,	1		},
		{ 547389,	2		},
		{ 547389,	256		},
		{ 547389,	4096	},
		{ 52532,	1<<4	},
		{ 52532,	1<<10	},
		{ 52532,	1<<16	},
	};
	static const struct
	{
		size_t		initialSize;
		size_t		newSize;
		size_t		alignment;
	} s_alignedReallocCases[] =
	{
		{ 1,		1,		1		},
		{ 1,		1,		2		},
		{ 1,		1,		256		},
		{ 1,		1,		4096	},
		{ 1,		1241,	1		},
		{ 1,		1241,	2		},
		{ 1,		1241,	256		},
		{ 1,		1241,	4096	},
		{ 547389,	234,	1		},
		{ 547389,	234,	2		},
		{ 547389,	234,	256		},
		{ 547389,	234,	4096	},
		{ 52532,	421523,	1<<4	},
		{ 52532,	421523,	1<<10	},
		{ 52532,	421523,	1<<16	},
	};

	int caseNdx;

	for (caseNdx = 0; caseNdx < DE_LENGTH_OF_ARRAY(s_alignedAllocCases); caseNdx++)
	{
		void* const		ptr		= deAlignedMalloc(s_alignedAllocCases[caseNdx].numBytes, s_alignedAllocCases[caseNdx].alignment);

		DE_TEST_ASSERT(ptr);
		DE_TEST_ASSERT(deIsAlignedPtr(ptr, s_alignedAllocCases[caseNdx].alignment));

		deMemset(ptr, 0xaa, s_alignedAllocCases[caseNdx].numBytes);

		deAlignedFree(ptr);
	}

	for (caseNdx = 0; caseNdx < DE_LENGTH_OF_ARRAY(s_alignedReallocCases); caseNdx++)
	{
		void* const		ptr		= deAlignedMalloc(s_alignedReallocCases[caseNdx].initialSize, s_alignedReallocCases[caseNdx].alignment);

		DE_TEST_ASSERT(ptr);
		DE_TEST_ASSERT(deIsAlignedPtr(ptr, s_alignedReallocCases[caseNdx].alignment));

		deMemset(ptr, 0xaa, s_alignedReallocCases[caseNdx].initialSize);

		{
			void* const		newPtr			= deAlignedRealloc(ptr, s_alignedReallocCases[caseNdx].newSize, s_alignedReallocCases[caseNdx].alignment);
			const size_t	numPreserved	= s_alignedReallocCases[caseNdx].newSize < s_alignedReallocCases[caseNdx].initialSize
											? s_alignedReallocCases[caseNdx].newSize
											: s_alignedReallocCases[caseNdx].initialSize;
			size_t			off;

			DE_TEST_ASSERT(newPtr);
			DE_TEST_ASSERT(deIsAlignedPtr(ptr, s_alignedReallocCases[caseNdx].alignment));

			for (off = 0; off < numPreserved; off++)
				DE_TEST_ASSERT(*((const deUint8*)newPtr + off) == 0xaa);

			deAlignedFree(newPtr);
		}
	}
}

DE_END_EXTERN_C
